﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.AccountManagement;
using System.DirectoryServices.ActiveDirectory;
using System.DirectoryServices;
using System.Diagnostics;

namespace MailboxRightsLookup
{
    class DomainUtils
    {
        // DomainUtils is a singleton (needs to mantain state)
        static DomainUtils _instance;

        public static DomainUtils Instance
        {
            get { return _instance == null ? _instance = new DomainUtils() : _instance; }
        }

        // This class holds 3 dictionaries to resolve domains names either by fqdn, netbios name
        // or ldap path and retrieve a corresponding PrincipalContext
        // PrincipalContexts are lazily instantiated and reused in subsequent calls
        public enum DomainNameType { Fqdn, Netbios, Path }
        Forest _forest;
        Dictionary<string, Lazy<PrincipalContext>> _fqdnDict;
        Dictionary<string, Lazy<PrincipalContext>> _netbiosDict;
        Dictionary<string, Lazy<PrincipalContext>> _pathDict;

        private DomainUtils()
        {
            _forest = Forest.GetCurrentForest();
            int domainCount = _forest.Domains.Count;
            _fqdnDict = new Dictionary<string, Lazy<PrincipalContext>>(domainCount);
            _netbiosDict = new Dictionary<string, Lazy<PrincipalContext>>(domainCount);
            _pathDict = new Dictionary<string, Lazy<PrincipalContext>>(domainCount);

            string fqdnName, container;
            foreach (Domain domain in _forest.Domains)
            {
                fqdnName = domain.Name.ToLower();
                container = String.Join(",", fqdnName.Split('.').Select((str) => "dc=" + str));
                Lazy<PrincipalContext> lazyContext = new Lazy<PrincipalContext>(() => LoadPrincipalContext(fqdnName, container));
                _fqdnDict.Add(fqdnName, lazyContext);
                _netbiosDict.Add(GetNetbiosName(domain).ToLower(), lazyContext);
                _pathDict.Add(String.Format("ldap://{0}/{1}", fqdnName, container), lazyContext);
            }
        }

        ~DomainUtils()
        {
            // Free allocated contexts
            foreach (var context in _netbiosDict.Values)
            {
                if (context.IsValueCreated)
                    context.Value.Dispose();
            }
        }

        public PrincipalContext GetPrincipalContext(string domainName, DomainNameType type)
        {
            string name = domainName.ToLower();
            switch (type)
            {
                case DomainNameType.Netbios:
                    return _netbiosDict[name].Value;
                case DomainNameType.Path:
                    return _pathDict[name].Value;
                case DomainNameType.Fqdn:
                default:
                    return _fqdnDict[name].Value;
            }
        }

        private PrincipalContext LoadPrincipalContext(string fqdnName, string container)
        {
            // Container binds to RootDSE
            return new PrincipalContext(ContextType.Domain, fqdnName, container);
        }




        // domain.Name shows Fqdn, in order to get Netbios name
        // http://blogs.edwardwilde.com/2009/12/14/lookup-the-netbios-name-of-a-domain-using-directory-services/
        public static string GetNetbiosName(Domain domain)
        {
            // Bind to RootDSE and grab the configuration naming context  
            DirectoryEntry rootDSE = new DirectoryEntry(@"LDAP://RootDSE");
            DirectoryEntry partitions = new DirectoryEntry(@"LDAP://cn=Partitions," + rootDSE.Properties["configurationNamingContext"].Value);

            DirectoryEntry domainEntry = domain.GetDirectoryEntry();

            //Iterate through the cross references collection in the Partitions container  
            DirectorySearcher directorySearcher = new DirectorySearcher(partitions)
               {
                   Filter = "(&(objectCategory=crossRef)(ncName=" +
                        domainEntry.Path
                            .Replace("LDAP://", string.Empty)
                            .Replace(domain.Name + "/", string.Empty) + "))",
                   SearchScope = SearchScope.Subtree
               };

            directorySearcher.PropertiesToLoad.Add("nETBIOSName");

            //Display result (should only be one)  
            SearchResultCollection results = directorySearcher.FindAll();
            if (results.Count == 0)
            {
                return null;
            }

            return results[0].Properties["nETBIOSName"][0].ToString();
        }  

        public static string GetDomainNameFromDN(string dn)
        {
            int dcPosition = dn.IndexOf(",dc=", StringComparison.CurrentCultureIgnoreCase) + 4;
            string domain = dn.Substring(dcPosition,
                dn.IndexOf(",dc=", dcPosition, StringComparison.CurrentCultureIgnoreCase) - dcPosition);
            return domain.ToUpper();
        }
    }
}
